package indi.latch;

import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;

/**
 * Hello world!
 */
public class App {

    public static void main(String[] args) {
        QueryRunnable queryRunnable2 = new QueryRunnable(10);
        QueryRunnable queryRunnable1 = new QueryRunnable(2);
        List<QueryRunnable> queryRunnables = new ArrayList<>();
        queryRunnables.add(queryRunnable2);
        queryRunnables.add(queryRunnable1);

        List<CompletableFuture> completableFutures = new ArrayList<>();

        long start = System.currentTimeMillis();
        Semaphore semaphore = new Semaphore(0);
        for (QueryRunnable queryRunnable : queryRunnables) {
            CompletableFuture completeFuture = queryRunnable.getCompleteFutureList();
            if (Objects.nonNull(completeFuture)) {
                completeFuture.thenRunAsync(semaphore::release);
                completableFutures.add(completeFuture);
            }
        }

        try {
            if (semaphore.tryAcquire(5, TimeUnit.SECONDS)) {
                int loop = 0;
                for (CompletableFuture completableFuture : completableFutures) {
                    loop++;
                    if (completableFuture.isDone()) {
                        try {
                            System.out.println("query supplier end, loop = " + loop);
                            completableFuture.get();
                            break;
                        } catch (InterruptedException | ExecutionException e) {
                            e.printStackTrace();
                        }
                    }
                }
            } else {
                System.out.println("query supplier timeout");
            }

            System.out.println("END TIME " + (System.currentTimeMillis() - start));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static class QuerySupplier implements Supplier {

        private int interval;

        public QuerySupplier(int interval) {
            this.interval = interval;
        }

        /**
         * Gets a result.
         *
         * @return a result
         */
        @Override
        public Object get() {
            long start = System.currentTimeMillis();
            try {
                TimeUnit.SECONDS.sleep(2 * interval);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("query supplier, interval = " + interval + ", cost = " + (System.currentTimeMillis() - start));
            return null;
        }
    }

    public static class QueryRunnable implements Runnable {

        private int interval;

        public QueryRunnable(int interval) {
            this.interval = interval;
        }

        /**
         * When an object implementing interface <code>Runnable</code> is used
         * to create a thread, starting the thread causes the object's
         * <code>run</code> method to be called in that separately executing
         * thread.
         * <p>
         * The general contract of the method <code>run</code> is that it may
         * take any action whatsoever.
         *
         * @see Thread#run()
         */
        @Override
        public void run() {
            System.out.println("query runnable");
        }

        public CompletableFuture getCompleteFutureList() {
            QuerySupplier querySupplier = new QuerySupplier(interval);
            return CompletableFuture.supplyAsync(querySupplier);
        }
    }
}
